package com.bytesizedhosting.api.android.darthcrap.api;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Date;

import simplexml.CannotParseException;
import simplexml.Node;
import simplexml.XMLDocument;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.util.Log;

import com.bytesizedhosting.api.android.darthcrap.Constants;
import com.bytesizedhosting.api.android.darthcrap.R;

public final class APIController {
	public static enum RetrievalState {
		READY     (-1                               , -1),
		LOADING   (R.string.loading_state_loading   , R.string.loading_state_noapikey_error),
		CONNECTING(R.string.loading_state_connecting, R.string.loading_state_connecting_error),
		RETRIEVING(R.string.loading_state_retrieving, R.string.loading_state_retrieving_error),
		PARSING   (R.string.loading_state_parsing   , R.string.loading_state_parsing_error),
		SAVING    (R.string.loading_state_saving    , R.string.loading_state_saving_error),
		FINISHING (R.string.loading_state_finishing , -1),
		KEYCHECK  (-1                               , R.string.loading_state_nosuchkey_error);
		
		private final int intErrorMessageID;
		private final int intNormalMessageID;
		
		private RetrievalState(int intNormalMessageID, int intErrorMessageID) {
			this.intNormalMessageID = intNormalMessageID;
			this.intErrorMessageID = intErrorMessageID;
		}
		
		public int getNormalMessageID() {
			return this.intNormalMessageID;
		}
		
		public int getErrorMessageID() {
			return this.intErrorMessageID;
		}
	}
	private static APIData objData = null; 
	
	public static APIData getData() {
		return objData;
	}
	
	public static boolean hasData() {
		return (objData != null);
	}
	
	public static boolean canLoadFile(Context objContext) {
		String[] arrFileList = objContext.fileList();
		for (String strFile : arrFileList) {
			if (strFile.equals(Constants.STORED_FILENAME)) {
				return true;
			}
		}
		return false;
	}
	
	public static boolean loadFile(Context objContext) {
		FileInputStream objFileStream = null;
		ObjectInputStream objObjectStream = null;
		try {
			objFileStream = objContext.openFileInput(Constants.STORED_FILENAME);
			objObjectStream = new ObjectInputStream(objFileStream);
			APIData objNewData = (APIData)objObjectStream.readObject();
			objObjectStream.close();
			objFileStream.close();
			objData = objNewData;
		}
		catch (IOException objException) {
			return false;
		}
		catch (ClassNotFoundException objException) {
			return false;
		}
		return true;
	}
	
	
	public static boolean saveFile(Context objContext) {
		FileOutputStream objFileStream = null;
		ObjectOutputStream objObjectStream = null;
		try {
			objFileStream = objContext.openFileOutput(Constants.STORED_FILENAME, Context.MODE_WORLD_READABLE);
			objObjectStream = new ObjectOutputStream(objFileStream);
			objObjectStream.writeObject(objData);
			objObjectStream.close();
			objFileStream.close();
		}
		catch (IOException objException) {
			Log.e(Constants.TAG, "APIC: Aborting save due to " + objException.getClass().getSimpleName() + " - " + objException.getMessage());			
			return false;
		}
		return true;
	}

	public static RetrievalController retrieve(final Context objContext, final APIRetriever objHandler, boolean boolRunInNewThread) {
		return retrieve(objContext, objHandler, boolRunInNewThread, false);
	}	
	
	public static RetrievalController retrieve(final Context objContext, final APIRetriever objHandler, boolean boolRunInNewThread, boolean boolForceWarningIfNecessary) { 
		RetrievalController objController = new RetrievalController(objContext,objHandler,boolForceWarningIfNecessary);

		if (boolRunInNewThread) {
			new Thread(objController).start();
		}
		else {
			objController.run();
		}
		
		return objController;
	}
	
	public static class RetrievalController implements Runnable {
		private Context objContext;
		private APIRetriever objHandler;
		private APIController.RetrievalState objState = null;
		private boolean boolHadError = false;
		private boolean boolRan = false;
		private boolean boolForceWarningIfNecessary = false;
		
		private RetrievalController(Context objContext, APIRetriever objHandler, boolean boolForceWarningIfNecessary) {
			this.objContext = objContext;
			this.objHandler = objHandler;
			this.boolForceWarningIfNecessary = boolForceWarningIfNecessary;
		}

		@Override
		public void run() {
			if (!this.boolRan) {
				this.boolRan = true;
				changeState(APIController.RetrievalState.LOADING,false,false);
				SharedPreferences objSettings = objContext.getSharedPreferences(Constants.PREFS_FILE, Context.MODE_PRIVATE);
				String strAPIKey = objSettings.getString(Constants.PREFS_KEY_APIKEY, "");
				if (strAPIKey.matches("[0-9a-f]{40}")) {
					changeState(APIController.RetrievalState.CONNECTING,false);
					try {
						URL objURL = new URL(Constants.API_URL+strAPIKey);
						URLConnection objOrdinaryConnection = objURL.openConnection();
						if (objOrdinaryConnection instanceof HttpURLConnection) {
							HttpURLConnection objConnection = (HttpURLConnection) objOrdinaryConnection;
							objConnection.setAllowUserInteraction(false);
							objConnection.setInstanceFollowRedirects(true);
							objConnection.setRequestMethod("GET");
							objConnection.connect();
							int intResponse = objConnection.getResponseCode();
							if (intResponse == HttpURLConnection.HTTP_OK) {
								InputStream objStream = objConnection.getInputStream();
								changeState(APIController.RetrievalState.RETRIEVING,false);
								boolean boolReady = true;
								XMLDocument objDocument = null;
								try {
									objDocument = new XMLDocument(objStream);
								}
								catch (CannotParseException objException) {
									Log.e(Constants.TAG, "APIC: Aborting retrieval due to " + objException.getClass().getSimpleName() + " - " + objException.getMessage());
									boolReady = false;
								}
								if (boolReady) {
									changeState(APIController.RetrievalState.PARSING,false);
									if (!objDocument.getName().equalsIgnoreCase("error")) {
										boolReady = true;
										try {
											APIData objNewData = new APIData(objDocument.node("login").getValue(),objDocument.node("balance").getValue());
											for (Node objNewsNode : objDocument.node("news_items").nodes("news_item")) {
												objNewData.addNewsItem(new APIData.NewsItem(objNewsNode.node("title").getValue(), objNewsNode.node("body").getValue(), objNewsNode.node("author").getValue()));
											}
											for (Node objAccountNode : objDocument.node("shared_accounts").nodes("shared_account")) {
												String strHardQuota = objAccountNode.node("hard_quota").getValue();
												String strServerName = objAccountNode.node("server_name").getValue();
												if (strServerName.equalsIgnoreCase("hebe") && objNewData.getLogin().equalsIgnoreCase("darthcrap")) {
													strHardQuota = "151.0";
												}
												objNewData.addAccount(new APIData.Account(strServerName, objAccountNode.node("cached_quota").getValue(), objAccountNode.node("percentage_quota").getValue(), objAccountNode.node("live_quota").getValue(), objAccountNode.node("soft_quota").getValue(), strHardQuota, objAccountNode.node("days_till_deletion").getValue(), objAccountNode.node("paid_till").getValue(), objAccountNode.node("web_vnc_link").getValue(), objAccountNode.node("web_ui_link").getValue(), objAccountNode.node("upgrade_available").getValue(), objAccountNode.node("client_id").getValue()));
											}
											objData = objNewData;
										}
										catch (Exception objException) {
											Log.e(Constants.TAG, "APIC: Aborting parsing due to " + objException.getClass().getSimpleName() + " - " + objException.getMessage());		
											boolReady = false;
											changeState(APIController.RetrievalState.PARSING,true);
										}
										if (boolReady) {
											changeState(APIController.RetrievalState.SAVING,false);
											if (saveFile(objContext)) {
												changeState(APIController.RetrievalState.FINISHING,false);
						        				if (APIController.getData().getBalance() < 0) {
						        					if (shouldShowBalanceNotification(objSettings) || this.boolForceWarningIfNecessary) {
						        						showBalanceNotification(objSettings,objContext);
						        					}
						        				}
												changeState(APIController.RetrievalState.READY,false);
											}
											else {
												changeState(APIController.RetrievalState.SAVING,true);	
											}
										}
									}
									else {
										changeState(APIController.RetrievalState.KEYCHECK,true);
									}
								}
								else {
									changeState(APIController.RetrievalState.RETRIEVING,true);									
								}
							}
							else {
								throw new IOException("Invalid response code - " + intResponse);
							}
						}
						else {
							throw new IOException("Not a HTTP connection");
						}
					}
					catch (MalformedURLException objException) {
						Log.e(Constants.TAG, "APIC: Aborting connection due to " + objException.getClass().getSimpleName() + " - " + objException.getMessage());
						changeState(APIController.RetrievalState.CONNECTING,true);
					}
					catch (IOException objException) {
						Log.e(Constants.TAG, "APIC: Aborting connection due to " + objException.getClass().getSimpleName() + " - " + objException.getMessage());
						changeState(APIController.RetrievalState.CONNECTING,true);
					}
				}
				else {
					changeState(APIController.RetrievalState.LOADING,true);
				}
			}
		}
		
		private boolean shouldShowBalanceNotification(SharedPreferences objPreferences) {
			long lngCurrentTime = new Date().getTime();
			long lngLastDisplayTime = objPreferences.getLong(Constants.PREFS_KEY_LAST_NOTIFICATION_TIME, 0);
			long lngDifference = lngCurrentTime-lngLastDisplayTime;
			return (lngDifference > 57150000);
		}

		private void showBalanceNotification(SharedPreferences objPreferences, Context objContext) {
			NotificationManager objManager = (NotificationManager) objContext.getSystemService(Context.NOTIFICATION_SERVICE);
			Notification objNotification = new Notification(R.drawable.icon_notification,objContext.getString(R.string.loading_notification_title),System.currentTimeMillis());
			objNotification.setLatestEventInfo(objContext.getApplicationContext(), objContext.getString(R.string.loading_notification_title), objContext.getString(R.string.loading_notification_contents), PendingIntent.getActivity(objContext, 0, new Intent(Intent.ACTION_VIEW,Uri.parse("http://bytesized-hosting.com/invoices")), Intent.FLAG_ACTIVITY_NEW_TASK));
			objNotification.defaults |= Notification.DEFAULT_LIGHTS;
			objNotification.vibrate = new long[] {0,200,200,200,200,200,200,200};
			objManager.notify(Constants.TAG, 1, objNotification);
			SharedPreferences.Editor objEditor = objPreferences.edit();
			objEditor.putLong(Constants.PREFS_KEY_LAST_NOTIFICATION_TIME, new Date().getTime());
			objEditor.commit();
		}

		private void changeState(APIController.RetrievalState objState, boolean boolHadError) {
			changeState(objState,boolHadError,true);
		}
		
		private void changeState(APIController.RetrievalState objState, boolean boolHadError, boolean boolDelay) {
			if (boolDelay) {
				try {
					Thread.sleep(500);
				}
				catch (InterruptedException objException) {}
			}
			
			this.objState = objState;
			this.boolHadError = boolHadError;
			if (this.objHandler != null) {
				this.objHandler.retrieveStateChanged(objState, boolHadError);
			}
		}
		
		public APIController.RetrievalState getState() {
			return this.objState;
		}
		
		public boolean hadError() {
			return this.boolHadError;
		}
	}
}